#-*-encoding:utf-8-*-

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET

import optparse
import ConfigParser
import os
import spill

class RiskRecognizer(object):

    network = dict()

    def __init__(self):
        pass

    @classmethod
    def load_network(cls, fname):
        cls.network = dict()

        tree = ET.ElementTree(file=fname)
        for child in tree.getroot():
            if child.tag == 'edge':
                attrib = child.attrib
                attrib.setdefault('speed', float(child[0].attrib['speed']))
                attrib.setdefault('length', float(child[0].attrib['length']))
                cls.network.setdefault(child.attrib['id'], attrib)
        return cls.network

    def process(self, user_gpx):
        '''
        :param user_gpx: type list [[userid, time, angle, speed, pos, lane], ...]
        :return:
        '''
        return None
    @staticmethod
    def extract_road(lane):
        assert type(lane) is str
        return "_".join(lane.split("_")[:-1])

    @staticmethod
    def calculate_distance(gpx, start, end):
        distance = 0
        l_shift = float(gpx[start][-2])
        l_road = RiskRecognizer.extract_road(gpx[start][-1])
        direction = True

        for i in range(start, end):
            c_point = gpx[i]
            c_shift = float(c_point[-2])
            c_road = RiskRecognizer.extract_road(c_point[-1])
            if l_road == c_road:
                if c_shift >= l_shift:
                    direction = True
                else:
                    direction = False
                distance += abs(c_shift - l_shift)
            else:
                if direction is True:
                    distance += float(RiskRecognizer.network[l_road]['length']) - l_shift + c_shift
                else:
                    distance += l_shift + c_shift
            l_shift = c_shift
            l_road = c_road
            assert distance >= 0
        return round(distance, 8)

class OverSpeedRecognizer(RiskRecognizer):
    def __init__(self, tThreshold, vRatio=1.0):
        RiskRecognizer.__init__(self)
        self.tThreshold = tThreshold
        self.vRatio = vRatio
        self.result = {}

    def process(self, user_gpx):

        inOverSpeed = 0
        current_result = []

        for i in range(len(user_gpx)):
            item = user_gpx[i]
            current_t = int(float(item[1]))
            speed = float(item[3])
            road = self.extract_road(item[-1])

            if road in self.network:
                limit = self.network[road]['speed']
                if speed > limit * self.vRatio:
                    inOverSpeed += 1
                else:
                    if inOverSpeed > self.tThreshold:
                        # mark as one over speed event
                        distance = self.calculate_distance(user_gpx, i - inOverSpeed - 1, i)
                        current_result.append([current_t - inOverSpeed, inOverSpeed, distance])
                    inOverSpeed = 0
            else:
                print("error road {0} not in network".format(road))

        self.result.setdefault(user_gpx[0][0], current_result)

    def write_result(self, fname, header=True):
        with open(fname, "wb") as fout:
            if header:
                fout.write("user,begin,duration,distance\n")
            for user, items in self.result.iteritems():
                for item in items:
                    fout.write("{0},{1},{2},{3}\n".format(user, item[0], item[1], item[2]))

class AbruptAccelerateRecognizer(RiskRecognizer):

    def __init__(self, accelerateThreshold, sign=1):
        RiskRecognizer.__init__(self)
        self.accelerateThreshold = accelerateThreshold
        self.result = {}
        self.sign = sign

    def process(self, user_gpx):
        user = user_gpx[0][0]
        inAbrupt = 0
        currentResult = []
        lastSpeed = None
        for i, item in enumerate(user_gpx):
            currentTime = int(float(item[1]))
            speed = float(item[3])
            if lastSpeed is not None:
                tmpAccelerate = speed - lastSpeed
                if tmpAccelerate * self.sign > self.accelerateThreshold:
                    inAbrupt += 1
                else:
                    if inAbrupt:
                        distance = self.calculate_distance(user_gpx, i - inAbrupt - 1, i)
                        currentResult.append([currentTime - inAbrupt, inAbrupt, distance])
                    inAbrupt = 0
            lastSpeed = speed
        self.result.setdefault(user, currentResult)

    def write_result(self, fname, header=True):
        with open(fname, "wb") as fout:
            if header:
                fout.write("user,begin,duration,distance\n")
            for user, items in self.result.iteritems():
                for item in items:
                    fout.write("{0},{1},{2},{3}\n".format(user, item[0], item[1], item[2]))


class AbruptTurnRecognizer(RiskRecognizer):
    def __init__(self, angleThreshold, speedThreshold):
        RiskRecognizer.__init__(self)
        self.angleThreshold = angleThreshold
        self.speedThreshold = speedThreshold
        self.result = {}

    def detect_turn(self, lAngle, cAngle):
        change = cAngle - lAngle
        if -change > self.angleThreshold or change > self.angleThreshold:
            return True
        return False

    def process(self, user_gpx):
        user = user_gpx[0][0]
        lAngle = float(user_gpx[0][2])
        begin_turn = 0
        inState = 0
        begin_p = 0
        currentResult = []
        beginAngle = 0
        for i, point in enumerate(user_gpx):
            cAngle = float(point[2])
            speed = float(point[3])
            currentTime = int(float(point[1]))
            if self.detect_turn(lAngle, cAngle):
                if begin_turn == 0:
                    begin_turn = 1
                    begin_p = i - 1
                    beginAngle = lAngle
                if speed > self.speedThreshold:
                    inState += 1
            else:
                if begin_turn:
                    # end turning
                    if inState == i - begin_p - 1 and cAngle - beginAngle >= 80:
                        currentResult.append([currentTime - inState - 1, inState])
                begin_turn = 0
                inState = 0
                begin_p = i
            lAngle = cAngle
        self.result.setdefault(user, currentResult)

    def write_result(self, fname, header=True):
        with open(fname, "wb") as fout:
            if header:
                fout.write("user,begin,duration\n")
            for user, items in self.result.iteritems():
                for item in items:
                    fout.write("{0},{1},{2}\n".format(user,item[0],item[1]))


class Subject(object):
    def __init__(self):
        self.observers = []

    def register(self, obj):
        assert isinstance(obj, RiskRecognizer)
        self.observers.append(obj)

    def remove(self, obj):
        assert isinstance(obj, RiskRecognizer)
        self.observers.remove(obj)

    def notify(self, args):
        # do notify observer
        for observer in self.observers:
            observer.process(args)

    def process(self, fname, header=False):
        users = {}
        with open(fname, "rb") as fin:
            for line in fin:
                if header:
                    header = False
                    continue
                elements = line.split(',')
                users.setdefault(elements[0], [])
                users[elements[0]].append(elements)   # aggregate one group

        for user in users:
            self.notify(users[user])


def parseConfiguration(conf=None):
    parser = ConfigParser.RawConfigParser()
    parser.read(conf)
    parameters = {}
    # global input dir only contains gps
    parameters.setdefault("input", parser.get("global", "input"))
    # set for  split GPS file into several subfiles
    parameters.setdefault("tmpDir", parser.get("global","tmpDir"))
    parameters.setdefault("spillNumber", parser.getint("global", "spillNumber"))
    parameters.setdefault("prefix", parser.get("global", "prefix"))
    parameters.setdefault("header", parser.getboolean("global", "header"))
    parameters.setdefault("network", parser.get("global", "network"))
    # deal with risk parameters
    parameters.setdefault("risk", {})

    # Over Speed
    parameters["risk"].setdefault("overSpeedRatio", parser.getfloat("risk", "overSpeedRatio"))
    parameters["risk"].setdefault("overSpeedDuration", parser.getint("risk", "overSpeedDuration"))
    parameters["risk"].setdefault("overSpeedOutput", parser.get("risk", "overSpeedOutput"))
    # abrupt accelerate
    parameters["risk"].setdefault("abruptAcc", parser.getfloat("risk", "abruptAcc"))
    parameters["risk"].setdefault("abruptAccOutput", parser.get("risk", "abruptAccOutput"))
    # abrupt decelerate
    parameters["risk"].setdefault("abruptDec", parser.getfloat("risk", "abruptDec"))
    parameters["risk"].setdefault("abruptDecOutput", parser.get("risk", "abruptDecOutput"))
    # abrupt turn
    parameters["risk"].setdefault("turnAngle", parser.getfloat("risk", "turnAngle"))
    parameters["risk"].setdefault("turnSpeed", parser.getfloat("risk", "turnSpeed"))
    parameters["risk"].setdefault("turnOutput", parser.get("risk", "turnOutput"))

    # deal with style parameters
    parameters.setdefault("style", {})

    # deal with profile parameters
    parameters.setdefault("profile", {})

    return parameters

def getOptions(args=None):
    optparser = optparse.OptionParser()
    optparser.add_option("-c", "--config", dest="config", help="the configuration file")
    (options, args) = optparser.parse_args(args)
    if not options.config:
        optparser.print_help()
        exit(0)

    return options

def process(options=None):
    parameters = parseConfiguration(options.config)
    #mkdir tmp
    if os.path.exists(parameters["tmpDir"]):
        print "Warning: tmpDir {0} exists!".format(parameters["tmpDir"])
        print "Delete files in tmpDir {0}".format(parameters["tmpDir"])
        for name in os.listdir(parameters["tmpDir"]):
            fname = os.path.join(parameters["tmpDir"], name)
            if os.path.isfile(fname):
                os.remove(fname)
    else:
        try:
            os.mkdir(parameters["tmpDir"])
        except OSError:
            print "Error: Create tmp dir error!"
            exit(1)

    header = parameters["header"]

    # split input gps file into several sub-files
    print "begin split file %s" % parameters["input"]
    prefix = "{0}/{1}".format(parameters["tmpDir"], parameters["prefix"])
    spill.split(parameters["input"], prefix, parameters["spillNumber"], header=header)
    print "Split file %s into directory %s finished!" % ( parameters["input"], parameters["tmpDir"])

    subject = Subject()
    fnames = []
    for name in os.listdir(parameters["tmpDir"]):
        fname = os.path.join(parameters["tmpDir"], name)
        if os.path.isfile(fname):
            fnames.append(fname)

    overSpeed = OverSpeedRecognizer(parameters["risk"]["overSpeedDuration"], parameters["risk"]["overSpeedRatio"])
    abruptAcc= AbruptAccelerateRecognizer(parameters["risk"]["abruptAcc"])
    abruptDec = AbruptAccelerateRecognizer(parameters["risk"]["abruptDec"], sign=-1)
    #print parameters["risk"]["turnSpeed"]
    abruptTurn = AbruptTurnRecognizer(parameters["risk"]["turnAngle"], parameters["risk"]["turnSpeed"])

    subject.register(overSpeed)
    subject.register(abruptAcc)
    subject.register(abruptDec)
    subject.register(abruptTurn)

    print "parse network"
    RiskRecognizer.load_network(parameters["network"])

    print "begin to do recognition for risk events"

    for path in fnames:
        subject.process(path, header=header)
        print("file {0} finished!".format(path))

    print "recognize the risk events finished!"

    overSpeed.write_result(parameters["risk"]["overSpeedOutput"], header=header)
    abruptAcc.write_result(parameters["risk"]["abruptAccOutput"], header=header)
    abruptDec.write_result(parameters["risk"]["abruptDecOutput"], header=header)
    abruptTurn.write_result(parameters["risk"]["turnOutput"], header=header)

if __name__ == '__main__':
    process(getOptions(args=None))